/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 *  Copyright 2008 Global Sign In
 *  Copyright 2009 Fabrice Colin
 * 
 *  This code is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <ctype.h>
#include <stdlib.h>
#include <sstream>
#include <iostream>
#include <fstream>
#include <algorithm>

#include "Base64.h"
#include "PHPSerializer.h"
#include "MessageDetails.h"

using std::clog;
using std::endl;
using std::string;
using std::vector;
using std::set;
using std::map;
using std::stringstream;
using std::for_each;
using std::ifstream;
using std::ios;

// A function object to delete attachments with for_each().
struct DeleteAttachmentFunc
{
	public:
		void operator()(vector<Attachment *>::value_type &p)
		{
			delete p;
		}

};

Attachment::Attachment(const string &filePath,
	const string &contentType,
	const string &contentId) :
	m_filePath(filePath),
	m_contentId(contentId),
	m_contentType(contentType),
	m_contentLength(0),
	m_pContent(NULL),
	m_encodedLength(0),
	m_pEncodedContent(NULL)
{
	attach();
}

void Attachment::attach(void)
{
	string fileName(getFileName());
#ifdef DEBUG
	clog << "Attachment: attaching " << fileName << endl;
#endif

	// What's the extension ?
	string::size_type lastPos = fileName.find_last_of('.');
	if ((lastPos != string::npos) &&
		(m_contentType.empty() == true))
	{
		string fileExtension(fileName.substr(lastPos + 1));

		// Guess the file type
		if (fileExtension == "pdf")
		{
			m_contentType = "application/pdf";
		}
		else if (fileExtension == "rtf")
		{
			m_contentType = "application/rtf";
		}
		else if (fileExtension == "doc")
		{
			m_contentType = "application/vnd.ms-word";
		}
		else if (fileExtension == "xls")
		{
			m_contentType = "application/vnd.ms-excel";
		}
		else if (fileExtension == "ppt")
		{
			m_contentType = "application/vnd.ms-powerpoint";
		}
		else if ((fileExtension == "html") ||
			(fileExtension == "htm"))
		{
			m_contentType = "text/html";
		}
		else if (fileExtension == "txt")
		{
			m_contentType = "text/plain";
		}
		else if (fileExtension == "gif")
		{
			m_contentType = "image/gif";
		}
		else if (fileExtension == "jpg")
		{
			m_contentType = "image/jpeg";
		}
		else if (fileExtension == "png")
		{
			m_contentType = "image/png";
		}
		else
		{
			m_contentType = "application/octet-stream";
		}
	}
}

Attachment::~Attachment()
{
	if (m_pContent != NULL)
	{
		delete[] m_pContent;
		m_pContent = NULL;
	}
	if (m_pEncodedContent != NULL)
	{
		delete[] m_pEncodedContent;
		m_pEncodedContent = NULL;
	}
}

string Attachment::getFileName(void) const
{
	string fileName("Attachment");

	// Get the actual file name
	string::size_type lastPos = m_filePath.find_last_of("/");
	if (lastPos != string::npos)
	{
		fileName = m_filePath.substr(lastPos + 1);
	}
	else if (m_filePath.empty() == false)
	{
		fileName = m_filePath;
	}

	return fileName;
}

bool Attachment::isInline(void) const
{
	if (m_contentId.empty() == false)
	{
		return true;
	}

	return false;
}

MessageDetails::MessageDetails() :
	m_personalizePlain(false),
	m_personalizeHtml(false),
	m_isReply(false),
	m_pPlainSub(NULL),
	m_pHtmlSub(NULL),
	m_isRecipientPersonalized(false),
	m_attachmentsCount(0),
	m_inlinePartsCount(0)
{
	// These are the fields to substitute, besides custom fields
	m_fieldNames.insert("{Name}");
	m_fieldNames.insert("{emailaddress}");
	m_fieldNames.insert("{recipientId}");
}

MessageDetails::~MessageDetails()
{
	if (m_pPlainSub != NULL)
	{
		delete m_pPlainSub;
	}
	if (m_pHtmlSub != NULL)
	{
		delete m_pHtmlSub;
	}

	for_each(m_attachments.begin(), m_attachments.end(),
		DeleteAttachmentFunc());
}

char *MessageDetails::loadRaw(const string &filePath, unsigned long &fileSize)
{
	ifstream dataFile;

	dataFile.open(filePath.c_str());
	if (dataFile.is_open() == true)
	{
		dataFile.seekg(0, ios::end);
		unsigned long length = dataFile.tellg();
		dataFile.seekg(0, ios::beg);

		char *pBuffer = new char[length + 1];
		dataFile.read(pBuffer, length);
		if (dataFile.fail() == false)
		{
			pBuffer[length] = '\0';
			dataFile.close();
			fileSize = length;

			return pBuffer;
		}

		dataFile.close();
	}

	return NULL;
}

string MessageDetails::encodeRecipientId(const string &recipientId)
{
	string idStr("&recipientId=");
	idStr += recipientId;
	idStr += "&";

	unsigned long dataLen = idStr.size();
	char *pData = Base64::encode(idStr.c_str(), dataLen);

	if ((pData == NULL) ||
		(dataLen == 0))
	{
		return "";
	}

	string encodedData(pData, dataLen);

	delete[] pData;

	return encodedData;
}

string MessageDetails::createMessageId(const string &suffix, bool isReplyId)
{
	string messageId;

	if (m_isReply == isReplyId)
	{
		messageId = m_msgIdPrefix;
	}
	if (messageId.empty() == true)
	{
		stringstream randomStr;

		randomStr << lrand48() << m_fromEmailAddress << lrand48();

		string pseudoName(randomStr.str());
		unsigned long msgIdLen = (unsigned long)pseudoName.length();
		char *pMessageId = Base64::encode(pseudoName.c_str(), msgIdLen);
		if (pMessageId != NULL)
		{
			messageId = pMessageId;
			delete[] pMessageId;

			// Remove non-alphanumerics
			string::size_type idLen = messageId.length();
			for (string::size_type pos = 0; pos < idLen; ++pos)
			{
				if (isalnum(messageId[pos]) == 0)
				{
					messageId.erase(pos, 1);
				}
			}
			if (idLen > 40)
			{
				messageId.resize(40);
			}
		}
	}
	messageId += suffix;
#ifdef DEBUG
	clog << "MessageDetails::createMessageId: ID " << messageId << endl;
#endif

	return messageId;
}

Substitute *MessageDetails::getPlainSubstituteObj(bool hasFields)
{
	if (m_pPlainSub == NULL)
	{
		m_pPlainSub = newSubstitute(m_plainContent,
			hasFields, false);
	}

	return m_pPlainSub;
}

Substitute *MessageDetails::getHtmlSubstituteObj(bool hasFields)
{
	if (m_pHtmlSub == NULL)
	{
		m_pHtmlSub = newSubstitute(m_htmlContent,
			hasFields, true);
	}

	return m_pHtmlSub;
}

string MessageDetails::substitute(const string &content,
	const map<string, string> &fieldValues)
{
	Substitute subObj(content, true, m_fieldNames, false);
	string subContent;

	subObj.substitute(fieldValues, subContent);

	return subContent;
}

bool MessageDetails::isRecipientPersonalized(void) const
{
	return m_isRecipientPersonalized;
}

void MessageDetails::setAttachments(const vector<string> &filePaths)
{
	// Delete and clear all attachments, if any
	for_each(m_attachments.begin(), m_attachments.end(),
		DeleteAttachmentFunc());
	m_attachments.clear();

	for (vector<string>::const_iterator fileIter = filePaths.begin();
		fileIter != filePaths.end(); ++fileIter)
	{
		newAttachment(new Attachment(*fileIter, "", ""));
	}
}

void MessageDetails::setAttachmentArray(const string &serializedArray)
{
	vector<string> filesToAttach;

	m_attachmentArray = serializedArray;
	if (PHPSerializer::unserializeArrayOfStrings(m_attachmentArray, filesToAttach) == false)
	{
		filesToAttach.clear();
	}

	setAttachments(filesToAttach);
}

void MessageDetails::loadAttachments(void)
{
	for (vector<Attachment *>::iterator attachmentIter = m_attachments.begin();
		attachmentIter != m_attachments.end(); ++attachmentIter)
	{
		Attachment *pAttachment = (*attachmentIter);
		if ((pAttachment == NULL) ||
			(pAttachment->m_filePath.empty() == true))
		{
			continue;
		}

		// Load the file
		if (pAttachment->m_contentLength == 0)
		{
			pAttachment->m_pContent = loadRaw(pAttachment->m_filePath, pAttachment->m_contentLength);
			if (pAttachment->m_pContent == NULL)
			{
				clog << "Couldn't load contents of " << pAttachment->m_filePath << endl;
			}
		}
	}
}

string MessageDetails::getAttachmentArray(void) const
{
	return m_attachmentArray;
}

unsigned int MessageDetails::getAttachmentCount(bool inlineParts) const
{
	if (inlineParts == true)
	{
		return m_inlinePartsCount;
	}

	return m_attachmentsCount;
}

Attachment *MessageDetails::getAttachment(unsigned int attachmentNum,
	bool inlineParts)
{
	vector<Attachment *>::iterator attachmentIter = m_attachments.begin();
	unsigned int pos = 0;

	while (attachmentIter != m_attachments.end())
	{
		if ((*attachmentIter)->isInline() != inlineParts)
		{
			// Skip this
			++attachmentIter;
			continue;
		}

		if (pos == attachmentNum)
		{
			Attachment *pAttachment = (*attachmentIter);

			return pAttachment;
		}

		// Next
		++attachmentIter;
		++pos;
	}

	return NULL;
}

bool MessageDetails::newAttachment(Attachment *pAttachment)
{
	if (pAttachment == NULL)
	{
		return false;
	}

	// What kind of attachment is it ?
	if (pAttachment->isInline() == true)
	{
		++m_inlinePartsCount;
	}
	else
	{
		++m_attachmentsCount;
	}

	m_attachments.push_back(pAttachment);

	return true;
}

Substitute *MessageDetails::newSubstitute(const string &contentTemplate, bool hasFields,
	bool escapeEntities)
{
	Substitute *pSub = new Substitute(contentTemplate, hasFields,
			m_fieldNames, escapeEntities);

	// Does it have any recipient-specific field ?
	if ((pSub->hasField("{Name}") == true) ||
		(pSub->hasField("{emailaddress}") == true) ||
		(pSub->hasField("{recipientId}") == true))
	{
		m_isRecipientPersonalized = true;
	}

	return pSub;
}

